<!doctype html>
<html lang="zh" class="no-js">
  <head>
    <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="generator" content="Hugo 0.75.1" />


<META NAME="ROBOTS" CONTENT="INDEX, FOLLOW">
<meta name="google-site-verification" content="9Ph9Wm3pZwsQkEkjY7ZubNjjiPp6nODqkLZDjJgoaCY" />



<link rel="shortcut icon" href="/favicons/favicon.ico?" >
<link rel="apple-touch-icon" href="/favicons/apple-touch-icon-180x180.png" sizes="180x180">
<link rel="icon" type="image/png" href="/favicons/favicon-16x16.png" sizes="16x16">
<link rel="icon" type="image/png" href="/favicons/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="/favicons/android-36x36.png" sizes="36x36">
<link rel="icon" type="image/png" href="/favicons/android-48x48.png" sizes="48x48">
<link rel="icon" type="image/png" href="/favicons/android-72x72.png" sizes="72x72">
<link rel="icon" type="image/png" href="/favicons/android-96x96.png" sizes="96x96">
<link rel="icon" type="image/png" href="/favicons/android-144x144.png" sizes="144x144">
<link rel="icon" type="image/png" href="/favicons/android-192x192.png" sizes="192x192">

<title>tcprt | 努力探求技术边界</title><meta property="og:title" content="tcprt" />
<meta property="og:description" content="Windows下基于tcp协议的traceroute实现" />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://gqw.github.io/blog/2020/12/29/tcprt/" />
<meta property="article:published_time" content="2020-12-29T00:00:00+00:00" />
<meta property="article:modified_time" content="2020-12-29T17:06:01+08:00" /><meta property="og:site_name" content="努力探求技术边界" />
<meta itemprop="name" content="tcprt">
<meta itemprop="description" content="Windows下基于tcp协议的traceroute实现">
<meta itemprop="datePublished" content="2020-12-29T00:00:00+00:00" />
<meta itemprop="dateModified" content="2020-12-29T17:06:01+08:00" />
<meta itemprop="wordCount" content="204">



<meta itemprop="keywords" content="" />
<meta name="twitter:card" content="summary"/>
<meta name="twitter:title" content="tcprt"/>
<meta name="twitter:description" content="Windows下基于tcp协议的traceroute实现"/>





<link rel="preload" href="/scss/main.min.276cf758ea51ad17445a946225f8c9b77391699167c3fb92541c8dfce581a44c.css" as="style">
<link href="/scss/main.min.276cf758ea51ad17445a946225f8c9b77391699167c3fb92541c8dfce581a44c.css" rel="stylesheet" integrity="">


<script
  src="https://code.jquery.com/jquery-3.5.1.min.js"
  integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
  crossorigin="anonymous"></script>



  </head>
  <body class="td-page td-blog">
    <header>
      
<nav class="js-navbar-scroll navbar navbar-expand navbar-dark flex-column flex-md-row td-navbar">
        <a class="navbar-brand" href="/">
		<span class="navbar-logo"><svg height="232pt" viewBox="0 0 232 232" width="232pt" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(.1 0 0 -.1 0 232)"><path d="m1385 2213c-22-20-68-49-103-65-73-35-137-40-282-20-189 26-321-20-336-118-5-33-9-36-40-36-64 0-130-93-188-266-29-86-48-221-41-298 9-114 18-162 30-166 7-3 30-1 52 5l39 11 22 96c21 90 68 198 111 253l18 23 6-47c17-118 89-171 231-169 65 1 119 11 276 54 161 45 209 54 275 54 144 0 205-61 205-204v-57l61-12c34-7 62-11 64-9 10 10 25 144 25 223 0 173-46 299-144 4e2l-55 56-54-31c-62-35-89-40-37-7 75 47 141 138 156 215l7 32-58-35c-66-39-139-58-207-53l-47 3 20 45c19 42 44 148 37 155-2 2-21-13-43-32z"/><path d="m516 1230c-49-10-95-21-102-24-16-6-19-76-4-76 17 0 50-63 50-94 0-68 33-149 79-197 56-57 103-73 222-73 73 0 93 4 142 28 97 47 167 157 167 261 0 44 1 45 33 45s32 0 33-53c1-75 36-148 99-206 65-58 120-76 231-75 170 2 255 80 279 254 8 63 27 105 50 114 11 4 15 16 13 38-3 32-5 33-78 50-170 41-351 33-479-20-89-37-211-38-285-4-111 52-292 65-450 32zm385-59c78-30 114-70 113-126-1-62-47-150-97-186-53-39-108-52-188-47-131 10-198 78-207 212-6 91 12 129 71 157 58 27 226 22 308-10zm708 11c73-36 97-114 67-215-22-76-64-123-129-142-1e2-30-201-14-266 41-75 64-114 187-76 241 40 57 145 91 280 92 67 1 99-4 124-17z"/><path d="m783 1105c-29-20-46-75-34-107 11-30 57-68 81-68 35 0 78 22 89 46 14 32 14 85-2 105-30 39-96 51-134 24z"/><path d="m1473 1105c-29-20-46-75-34-106 5-13 19-35 31-48 19-19 30-22 64-18 54 7 86 44 86 97 0 70-89 116-147 75z"/><path d="m459 537c-25-7-59-23-76-35-89-63-92-230-6-301 60-49 134-59 231-29l32 10v74c0 59 3 74 15 74 8 0 15 5 15 10 0 6-33 10-80 10s-80-4-80-10c0-5 11-10 25-10 25 0 25-1 25-80v-80h-35c-77 0-125 71-125 182 1 116 53 183 138 176 34-3 37-6 51-50 10-34 20-48 33-48 15 0 18 8 18 45 0 38-4 47-23 54-45 17-112 20-158 8z"/><path d="m960 538c-77-21-124-92-124-188 0-84 31-145 91-178 29-16 45-33 53-56 15-48 84-73 150-57l35 9-41 1c-44 1-70 21-76 58-2 18 6 27 42 45 71 36 105 116 94 215-8 71-63 135-128 152-51 13-49 13-96-1zm89-18c32-16 51-83 51-173-1-134-39-192-116-173-77 20-90 288-17 344 20 14 57 16 82 2z"/><path d="m1367 525c18-13 24-26 38-85 2-8 22-72 43-142 22-70 37-130 34-133s6-5 21-5c24 0 28 7 52 88 48 162 62 198 69 181 4-9 24-72 44-140 31-103 41-125 58-127 18-3 24 10 53 110 74 260 70 248 96 248 14 0 25 5 25 10 0 6-27 10-60 10s-60-4-60-10c0-5 12-10 26-10 22 0 24-3 18-22-4-13-20-72-35-131-15-60-29-111-32-113-3-4-48 141-62 203-2 10-7 28-11 41-5 19-3 22 20 22 14 0 26 5 26 10 0 6-33 10-80 10-44 0-80-4-80-9s11-11 23-13l24-3-29-1e2c-16-55-31-111-34-124-11-52-18-41-46 65-42 159-42 164-18 164 11 0 20 5 20 10 0 6-34 10-82 10-76 0-81-1-61-15z"/></g></svg></span><span class="text-uppercase font-weight-bold">努力探求技术边界</span>
	</a>
	<div class="td-navbar-nav-scroll ml-md-auto" id="main_navbar">
		<ul class="navbar-nav mt-2 mt-lg-0">
			
			
			<li class="nav-item mr-4 mb-2 mb-lg-0">
				
				
				
				
				
				
				<a class="nav-link active" href="/blog/" ><span class="active">博客</span></a>
			</li>
			
			<li class="nav-item mr-4 mb-2 mb-lg-0">
				
				
				
				
				
				
				<a class="nav-link" href="/about/" ><span>关于</span></a>
			</li>
			
			
			
		</ul>
	</div>
	<div class="navbar-nav d-none d-lg-block">
<input type="search" class="form-control td-search-input" placeholder="&#xf002 站内搜索…" aria-label="站内搜索…" autocomplete="off">

</div>
</nav>

    </header>
    <div class="container-fluid td-outer">
      <div class="td-main">
        <div class="row flex-xl-nowrap">
          <div class="col-12 col-md-3 col-xl-2 td-sidebar d-print-none">
            




<div id="td-sidebar-menu" class="td-sidebar__inner">
  
  <form class="td-sidebar__search d-flex align-items-center">
    
<input type="search" class="form-control td-search-input" placeholder="&#xf002 站内搜索…" aria-label="站内搜索…" autocomplete="off">


    <button class="btn btn-link td-sidebar__toggle d-md-none p-0 ml-3 fas fa-bars" type="button" data-toggle="collapse" data-target="#td-section-nav" aria-controls="td-docs-nav" aria-expanded="false" aria-label="Toggle section navigation">
    </button>
  </form>
  
  <nav class="collapse td-sidebar-nav" id="td-section-nav">
    
    






<ul class="td-sidebar-nav__section pr-md-3">
  <li class="td-sidebar-nav__section-title">
    <a  href="/blog/" class="align-left pl-0 pr-2 active td-sidebar-link td-sidebar-link__section">博客</a>
  </li>
  <ul>
    <li class="collapse show" id="blog">
      
      
      
      
      
      
      <a class="td-sidebar-link td-sidebar-link__page  active" id="m-blog20201229tcprt" href="/blog/2020/12/29/tcprt/">tcprt</a>
      
      
      
      
      
      <a class="td-sidebar-link td-sidebar-link__page " id="m-blog20201118c-e5ba8fe58897e58c96e5928ce58f8de5ba8fe58897e58c96e5b7a5e585b7" href="/blog/2020/11/18/c-%E5%BA%8F%E5%88%97%E5%8C%96%E5%92%8C%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%B7%A5%E5%85%B7/">C&#43;&#43;序列化和反序列化工具</a>
      
      
      
      
      
      <a class="td-sidebar-link td-sidebar-link__page " id="m-blog20201001e4bb8ehellowolde5bc80e5a78be6b7b1e585a5e6b585e587bac-20-coroutine-ts" href="/blog/2020/10/01/%E4%BB%8Ehellowold%E5%BC%80%E5%A7%8B%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BAc-20-coroutine-ts/">C&#43;&#43; 20 Coroutine</a>
      
      
    </li>
  </ul>
</ul>

  </nav>
</div>




          </div>
          <div class="d-none d-xl-block col-xl-2 td-toc d-print-none">
            






<div class="td-page-meta ml-2 pb-1 pt-2 mb-0" style="display: none">





<a href="https://github.com/gqw/edit/master/content/zh/blog/C&#43;&#43;/tcprt.md" target="_blank"><i class="fa fa-edit fa-fw"></i> 编辑此页</a>
<a href="https://github.com/gqw/issues/new?title=tcprt" target="_blank"><i class="fab fa-github fa-fw"></i> 提交文档问题</a>


<a href="https://github.com/gqw/issues/new" target="_blank"><i class="fas fa-tasks fa-fw"></i> 提交项目问题</a>

</div>






<nav id="TableOfContents">
  <ul>
    <li><a href="#介绍">介绍</a></li>
    <li><a href="#目标">目标</a></li>
    <li><a href="#原理与实现">原理与实现</a>
      <ul>
        <li><a href="#tracerouter原理">tracerouter原理</a></li>
        <li><a href="#实现">实现</a></li>
      </ul>
    </li>
    <li><a href="#优势与不足">优势与不足</a>
      <ul>
        <li><a href="#优势">优势</a></li>
        <li><a href="#缺点">缺点</a></li>
      </ul>
    </li>
    <li><a href="#使用">使用</a></li>
    <li><a href="#计划内容">计划内容</a></li>
    <li><a href="#参考文献">参考文献</a></li>
  </ul>
</nav>



          </div>
          <main class="col-12 col-md-9 col-xl-8 pl-md-5 pr-md-4" role="main">
            <a class="btn btn-lg td-rss-button d-none d-lg-block" href="https://gqw.github.io/blog/index.xml" target="_blank" >
             <img src="http://www.runoob.com/images/rss.gif" width="36" height="14">
            </a>
            
<div class="td-content">
	<h1>tcprt</h1>
	<div class="lead">Windows下基于tcp协议的traceroute实现</div>
	<div class="td-byline mb-4">
		By <b>顾起威 (<a href="https://gqw.github.io">@gqw</a>)</b> |
		<time datetime="2020-12-29" class="text-muted">2020-12-29</time>
	</div>
	
	<h1 id="tcprt">tcprt</h1>
<h2 id="介绍">介绍</h2>
<hr>
<p>产品发布后有玩家反映客户端（Windows系统）无法连接服务器， 使用<code>ping.exe</code>、<code>tracert.exe</code>工具检查均未发现有异常情况，但是<code>telnet</code>却无法连接。最后发现发现国内某些运营商网络在对<code>icmp</code>和<code>tcp</code>协议的路由选择上不一致导致。最后运维人员希望能够收集出错时的路由信息。
我们知道在Linux系统上实现很简单系统自带的<code>traceroute</code>工具自带tcp协议的探测，实现起来比较简单。但是客户端产品大多是Windows系统的，自带的<code>tracert.exe</code>只有icmp探测类型。下图是分别使用<code>ICMP</code>和<code>TCP</code>tracerouter的对比，图中可以看到明显的不同路由路径。
<img src="https://i.loli.net/2020/12/29/3ZTXhn2gspQ7vKw.png" alt="icmp和tcp tracerouter对比"></p>
<h2 id="目标">目标</h2>
<hr>
<ol>
<li>实现类似于linux的<code>traceroute -T</code>方法</li>
</ol>
<h2 id="原理与实现">原理与实现</h2>
<hr>
<h3 id="tracerouter原理">tracerouter原理</h3>
<hr>
<p>为了理解tracerouter原理，我们先来看下IP的协议头：
<img src="https://i.loli.net/2020/12/29/nbiz1smqjCDvKuB.png" alt="icmp和tcp tracerouter对比">
这里面有个8比特的Time To Live (TTL)字段，它代表是报文的存活时间，这个8位字段避免报文在互联网中永远存在（例如陷入路由环路）。存活时间以秒为单位，但小于一秒的时间均向上取整到一秒。在现实中，这实际上成了一个跳数计数器：报文经过的每个路由器都将此字段减1，当此字段等于0时，报文不再向下一跳传送并被丢弃，最大值是255。常规地，一份ICMP报文被发回报文发送端说明其发送的报文已被丢弃。这也是traceroute的核心原理。
什么意思呢？我们看下下面的图：
<img src="https://i.loli.net/2020/12/29/kOhK8HfZVPEDmG4.png" alt="icmp和tcp tracerouter对比">
这里表示的是192.168.1.2这台客户机访问192.168.5.32的路由图，报文首先在客户机（1.2）这台机器被创建出来并设置TTL字段为128, 然后经过若干路由接力传递最终到达目标服务器，在接力过程中没遇到一个路由中转，路由器均会将TTL值减一。再看这张图：
<img src="https://i.loli.net/2020/12/29/GaN2QUP1rtpge96.png" alt="icmp和tcp tracerouter对比">
这次我们在客户机组装报文时将TTL设置的比较小只有3，这样在报文到达第一个路由路时路由检查报文中TTL的值发现TTL大于1于是修改报文将TTL减1继续转发给下一个路由，第二个路由采用同样的方法修改TTL后继续发给第三个路由，第三个路由受到报文时同样检查TTL值，发现TTL值已经为1，意识到自己已经是最后一跳不能再继续转发了，所以直接向源主机发送类型为11的ICMP报文告诉源主机报文发送失败（当然返回的报文也需要若干路由跳转，图中返回的虚箭头指示表达报文最终到达源主机）。</p>
<p>traceroute为了探测每个路由的地址，循环递增报文的TTL值，当TTL等于1时第一个路由便返回ICMP报文，源机器便可根据其IP头获得路由地址，当TTL等于2时可以得到第二个路由地址，依次循环下去直到到达目标机器。</p>
<h3 id="实现">实现</h3>
<hr>
<p>tracert在Windows下只有基于ICMP协议的实现。Linux下倒是有TCP版本的实现，</p>
<div class="highlight"><pre style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-sh" data-lang="sh">traceroute -T www.baidu.com <span style="color:#0000cf;font-weight:bold">80</span>
</code></pre></div><p>既然如此那就看看源码，借鉴下别人是怎么实现的。Linux下traceroute<sup><a href="#ref_3">[3]</a></sup>的思路比较直接，使用原始套接字（SOCK_RAW）向目的主机发送tcp连接, 并递增请求包的TTL值，再用recvmsg中flags设置MSG_ERRQUEUE选项来获取连接信息。</p>
<p>但是在Windows系统下就没有那么幸运了。系统为了安全考虑（主要怕原始套接字被滥用）禁止了以下行为<sup><a href="#ref_1">[1]</a></sup>：</p>
<ol>
<li>
<p>TCP data cannot be sent over raw sockets.</p>
</li>
<li>
<p>A call to the bind function with a raw socket for the IPPROTO_TCP protocol is not allowed.</p>
</li>
</ol>
<p>而且recvmsg也没有MSG_ERRQUEUE可以使用。所以直接照搬Linux下traceroute的想法没法设施。</p>
<p>既然系统把路堵住了，有没有办法绕开去呢？其实是有的，在微软的文档中提到<sup><a href="#ref_1">[1]</a></sup>：</p>
<blockquote>
<p>On Windows Server 2003 and earlier, a Transport Driver Interface (TDI) provider and a Winsock helper DLL can be written to support the network protocol. The network protocol would then be added to the Winsock catalog as a supported protocol. This allows multiple applications to open sockets for this specific protocol and the device driver can keep track of which socket receives specific packets and errors.</p>
</blockquote>
<p>也就是需要你自己开发个驱动。好在有人已经实现了这样的功能，<code>winpcap</code>便是这样的网络库，也有人基于此开发了Windows下基于tcp协议的traceroute<sup><a href="#ref_2">[2]</a></sup>。但是为了这么一个简单的功能开发让用户安装如此危险的驱动程序，对用户来说是十分不友好的（winpcap静默安装是收费功能）。</p>
<p>还有其它方法吗？让我们回过头来仔细分析下思路。我们想要探测只需要每次发送的网络包中的ttl逐渐增大便可以了。系统限制了我们直接组装TCP网络包的能力，我们能否利用现有的资源呢？</p>
<ol>
<li>我们正常建立TCP连接总还是可以的，只是我们有手段修改其TTL的值吗？
幸运的是可以的。(Lucky + 1)</li>
<li>发送的数据有了，接收有吗？TTL值为0时路由会返回ICMP包吗？
同自己组装的效果一样，也是有的。(Lucky + 1)</li>
<li>我们没有MSG_ERRQUEUE，可以获得响应内容吗？
回答是Yes。 (Lucky + 1)</li>
</ol>
<p>有了上面的3个幸运值，我们就可以实现我们的目的了，思路如下：</p>
<ol>
<li>建立正常的TCP套接字进行connect连接，不过调用前通过修改socket选项修改TTL值。</li>
<li>另外再建立个原始套接字，监听所有响应。</li>
<li>收到任何响应关闭TCP套接字。</li>
</ol>
<p>实现细节<a href="https://github.com/gqw/tcprt">参考代码</a>。</p>
<p>下面说下实现时的一些注意点：</p>
<ol>
<li>修改TTL方法：
<div class="highlight"><pre style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-CPP" data-lang="CPP"><span style="color:#000">setsockopt</span> <span style="color:#000;font-weight:bold">(</span><span style="color:#000">tcp_sk</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">IPPROTO_IP</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">IP_TTL</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000;font-weight:bold">(</span><span style="color:#204a87;font-weight:bold">char</span><span style="color:#ce5c00;font-weight:bold">*</span><span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">&amp;</span><span style="color:#000">ttl</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#204a87;font-weight:bold">sizeof</span> <span style="color:#000;font-weight:bold">(</span><span style="color:#000">ttl</span><span style="color:#000;font-weight:bold">))</span>
</code></pre></div></li>
<li>设置连接socket为非阻塞模式，这样connect后可以立即监听网络响应。</li>
<li>因为是并发请求所以需要在connect之前bind一下，这样可以在探测结束前一直占用一个本地端口号，后面也可以根据这个端口进行信息匹配。</li>
<li>监听的原始套接字通过下面代码创建：
<div class="highlight"><pre style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-CPP" data-lang="CPP"><span style="color:#000">socket</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">AF_INET</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">SOCK_RAW</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">IPPROTO_IP</span><span style="color:#000;font-weight:bold">)</span>
</code></pre></div></li>
<li>原始套接字需要打开 SIO_RCVALL 选项，这样才能收到所有网络包，包括connect连接成功后的syn+ack包。</li>
<li>打开 SIO_RCVALL 选项需要先进行bind操作才可以，不然会报10022错误。</li>
<li>因为打开了 SIO_RCVALL 所以每个探测线程会收到bind地址的所有网络包，所以要根据协议头进行过滤和区分。前面占用的本地端口是探测线程的一个重要依据。</li>
</ol>
<h2 id="优势与不足">优势与不足</h2>
<hr>
<h3 id="优势">优势</h3>
<ol>
<li>并发探测，探测时间与ttl值无关，速度相对于windows系统自带命令快N倍。</li>
<li>代码精简（不到500行代码），不依赖任何第三方库。</li>
</ol>
<h3 id="缺点">缺点</h3>
<ol>
<li>由于使用原始套接字（SOCK_RAW）代码执行需要管理员权限。</li>
<li>由于并发请求，无法做到及时停止探测，会造成很多无用的探测。</li>
</ol>
<h2 id="使用">使用</h2>
<hr>
<div class="highlight"><pre style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-sh" data-lang="sh">git clone https://github.com/gqw/tcprt.git
<span style="color:#204a87">cd</span> tcprt
mkdir build
<span style="color:#204a87">cd</span> build
cmake ..
</code></pre></div><pre><code>./tcprt.exe 180.101.49.11 80 30
</code></pre><p><img src="https://i.loli.net/2020/12/29/4lapfrqPQeZCLoB.png" alt=""></p>
<h2 id="计划内容">计划内容</h2>
<hr>
<ol>
<li>支持ipv6</li>
<li>支持域名解析</li>
</ol>
<h2 id="参考文献">参考文献</h2>
<p>[1]  <a id="ref_1" href="https://docs.microsoft.com/en-us/windows/win32/winsock/tcp-ip-raw-sockets-2" >TCP/IP Raw Sockets</a><code>[EB/OL].</code>  <a href="https://docs.microsoft.com/en-us/windows/win32/winsock/tcp-ip-raw-sockets-2">https://docs.microsoft.com/en-us/windows/win32/winsock/tcp-ip-raw-sockets-2</a></p>
<p>[2]  <a id="ref_2" href="https://github.com/0xcafed00d/tracetcp" >tracetcp</a><code>[EB/OL].</code>  <a href="https://github.com/0xcafed00d/tracetcp">https://github.com/0xcafed00d/tracetcp</a></p>
<p>[3]  <a id="ref_3" href="http://ftp.oregonstate.edu/.2/lfs-website/blfs/view/svn/basicnet/traceroute.html" >traceroute</a><code>[EB/OL].</code>  <a href="http://ftp.oregonstate.edu/.2/lfs-website/blfs/view/svn/basicnet/traceroute.html">http://ftp.oregonstate.edu/.2/lfs-website/blfs/view/svn/basicnet/traceroute.html</a></p>

	

	<ul class="list-unstyled d-flex justify-content-between align-items-center mb-0 pt-5">
  <li>
    <a href="/blog/2020/11/18/c-%E5%BA%8F%E5%88%97%E5%8C%96%E5%92%8C%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%B7%A5%E5%85%B7/" class="btn btn-primary "><span class="mr-1">←</span> 上一页</a>
  </li>
    <a  class="btn btn-primary  disabled">下一页 <span class="ml-1">→</span></a>
  </li>
</ul>

</div><div id="utter-container"></div>
    <script src="https://utteranc.es/client.js"
        repo= 'gqw/hugocomment'
        issue-term= "title"
        theme= 'github-light'
        crossorigin= "anonymous"
        async>
    </script>


          </main>
        </div>
      </div>
      
<footer class="bg-dark py-5 row d-print-none">
  <div class="container-fluid mx-sm-5">
    <div class="row">
      <div class="col-6 col-sm-4 text-xs-center order-sm-2">
        
        
        
<ul class="list-inline mb-0">
  
  <li class="list-inline-item mx-2 h3" data-toggle="tooltip" data-placement="top" title="User mailing list" aria-label="User mailing list">
    <a class="text-white" target="_blank" href="gqwmail@qq.com">
      <i class="fa fa-envelope"></i>
    </a>
  </li>
  
</ul>

        
        
      </div>
      <div class="col-6 col-sm-4 text-right text-xs-center order-sm-3">
        
        
        
<ul class="list-inline mb-0">
  
  <li class="list-inline-item mx-2 h3" data-toggle="tooltip" data-placement="top" title="GitHub" aria-label="GitHub">
    <a class="text-white" target="_blank" href="https://github.com/gqw">
      <i class="fab fa-github"></i>
    </a>
  </li>
  
</ul>

        
        
      </div>
      <div class="col-12 col-sm-4 text-center py-2 order-sm-2">
        <small class="text-white">&copy; 2020 The Docsy Authors All Rights Reserved</small>
        
		
			<p class="mt-2"><a href="/about/">关于</a></p>
		
      </div>
    </div>
  </div>
</footer>


<script type="text/javascript"
  src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
</script>

<script type="text/x-mathjax-config">
  MathJax.Hub.Config({
    tex2jax: {
      inlineMath: [['$','$']],
      displayMath: [['$$','$$']],
      processEscapes: true,
      processEnvironments: true,
      skipTags: ['script', 'noscript', 'style', 'textarea', 'pre'],
      TeX: { equationNumbers: { autoNumber: "AMS" },
          extensions: ["AMSmath.js", "AMSsymbols.js"] }
      }
  });
  </script>

    </div>
    
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>







<script src="/js/main.min.b99ff36446a5ac4634439b55123fb4bc85e1a0423d2b19b088890d1e33f503a6.js" integrity="sha256-uZ/zZEalrEY0Q5tVEj&#43;0vIXhoEI9KxmwiIkNHjP1A6Y=" crossorigin="anonymous"></script>



  </body>
</html>